using System;
using System.Collections.Generic;
using CLELCore.Assembly.CLELInstructions;
using CLELCore.Assembly.MethodHeaderDataSections;
using CLELCore.Assembly.MethodHeaderDataSections.MethodHeaderDataSectionRows;

namespace CLELCore.Assembly
{
	public class MethodHeader
	{
		public const int METHODHEADER_SMALL_HEADER_SIZE = 0x1;
		public const int METHODHEADER_FAT_HEADER_SIZE = 0xc;

		public const int METHODTYPE_MASK = 0x3;
		public const int TINY_METHOD = 0x2;
		public const int FAT_METHOD = 0x3;
		public const int MORE_SECTS = 0x8;
		public const short DEFAULT_TINY_MAXSTACK = 0x8;
		public const int MAX_TINY_CODE_SIZE = 63; //6 bits = 2^6-1 (0 - 63)

		//private int _rva;
		private bool _tiny;
		private int _flags;
		private int _size;
		private short _max_stack;
		private int _code_size;
		private int _local_var_sig_token;
		private List<CLELInstruction>  _code;
		private List<MethodHeaderDataSection> _data_sections;

		/*
		public int RVA
		{
			get
			{
				return _rva;
			}
			set
			{
				_rva = value;
			}
		}
		*/
		public int Flags
		{
			get
			{
				return _flags;
			}
			set
			{
				_flags = value;
			}
		}
		public int Size
		{
			get
			{
				return _size;
			}
			set
			{
				_size = value;
			}
		}
		public short MaxStack
		{
			get
			{
				return _max_stack;
			}
			set
			{
				_max_stack = value;
				UpdateFlags();
			}
		}
		public int CodeSize
		{
			get
			{
				return _code_size;
			}
			set
			{
				_code_size = value;
				UpdateFlags();
			}
		}
		public int LocalVarSigToken
		{
			get
			{
				return _local_var_sig_token;
			}
			set
			{
				_local_var_sig_token = value;
				UpdateFlags();
			}
		}
		public List<CLELInstruction> Code
		{
			get
			{
				return new List<CLELInstruction>(_code);
			}
			set
			{
				if(value != null)
				{
					_code = new List<CLELInstruction>(value);
					UpdateFlags();
				}
			}
		}
		public List<MethodHeaderDataSection> DataSections
		{
			get
			{
				return new List<MethodHeaderDataSection>(_data_sections);
			}
			set
			{
				if(value != null)
				{
					_data_sections = new List<MethodHeaderDataSection>(value);
					UpdateFlags();
				}
			}
		}

		private void UpdateFlags()
		{
			bool new_tiny = IsTiny();
			if(_tiny && !new_tiny)
			{
				//tiny -> fat
				_flags = 0x3013; //default fat flags
				_size = 0x3; //fat is always 3 ints long
			}
			else if(!_tiny && new_tiny)
			{
				//fat -> tiny
				_flags = MethodHeader.TINY_METHOD;
				_size = 0;
			}
			_tiny = new_tiny;
		}

		public MethodHeader(bool tiny,int flags,int size,short max_stack,int code_size,
			int local_var_sig_token,List<CLELInstruction> code,
			List<MethodHeaderDataSection> sects)
		{
			//_rva = rva;
			_tiny = tiny;
			_flags = flags;
			_code_size = code_size;
			_max_stack = max_stack;
			_code = new List<CLELInstruction>(code);
			_local_var_sig_token = local_var_sig_token;
			_size = size;
			if(sects != null)
				_data_sections = new List<MethodHeaderDataSection>(sects);
		}

		public bool IsTiny()
		{
			if(_max_stack > DEFAULT_TINY_MAXSTACK)
				return false;
			if(_local_var_sig_token != 0)
				return false;
			if(_data_sections != null && _data_sections.Count > 0)
				return false;

			int i,code_len = 0;
			
			int count = _code.Count;
			for(i = 0;i < count;i++)
			{
				code_len += _code[i].Length;
				if(code_len > MAX_TINY_CODE_SIZE)
					return false;
			}
			return true;
		}

		private int GetDataSectionSize()
		{
			int ret = 0;
			if(_data_sections != null)
			{
				int i,section_count = _data_sections.Count;
				for(i = 0;i < section_count;i++)
				{
					ret += _data_sections[i].Size;
				}
			}
			return ret;
		}

		public byte[] GetBytes(bool little_endian)
		{
			byte[] ret = null;
			byte[] code = null;
			int len;
			if(_tiny)
			{
				code = CLELInstructionConvertor.ToBytes(_code,little_endian);
				len = code.Length;
				ret = new byte[len+1];
				ret[0] = (byte)(TINY_METHOD|(len<<2));
				Bits.SetBytes(1,ret,code);
			}
			else
			{
				code = CLELInstructionConvertor.ToBytes(_code,little_endian);
				len = code.Length;
				ret = new byte[len+12];
				Bits.SetShort(0,ret,(short)_flags,little_endian);
				Bits.SetShort(2,ret,_max_stack,little_endian);
				Bits.SetInt(4,ret,len,little_endian);
				Bits.SetInt(8,ret,_local_var_sig_token,little_endian);
				Bits.SetBytes(12,ret,code);

				if((_flags & MethodHeader.MORE_SECTS) == MethodHeader.MORE_SECTS)
				{
					byte[] data_section = new byte[GetDataSectionSize()];
					int i,index=0;
					int section_count = _data_sections.Count;
					MethodHeaderDataSection mhds;
					for(i = 0;i < section_count;i++)
					{
						mhds = _data_sections[i];
						Bits.SetBytes(index,data_section,mhds.GetBytes(little_endian));
						index += mhds.Size;
					}
					int ret_len = ret.Length;
					int align = Bits.AlignToFourByteBoundry(ret_len);
					int padding = align - ret_len;
					byte[] temp = new byte[data_section.Length+align];
					ret.CopyTo(temp,0);
					for(i = 0;i < padding;i++)
					{
						temp[ret_len+i] = 0;
					}
					data_section.CopyTo(temp,align);
					ret = temp;
				}
			}
			return ret;
		}

		public static MethodHeader ParseMethodHeader(byte[] buffer,int offset,bool little_endian)
		{
			bool tiny = true;
			int type = ((int)buffer[offset]) & 0xff;
			int i,flags,size,code_size,local_var_sig_token;
			List<MethodHeaderDataSection> sects = null;
			short max_stack;
			byte[] byte_code;
			List<CLELInstruction> code;
			if((type & MethodHeader.METHODTYPE_MASK) == MethodHeader.TINY_METHOD)
			{
				size = 0;
				local_var_sig_token = 0;
				flags = MethodHeader.TINY_METHOD;
				code_size = type >> 2;
				max_stack = MethodHeader.DEFAULT_TINY_MAXSTACK;
				byte_code = new byte[code_size];
				for(i = 0;i < code_size;i++)
				{
					byte_code[i] = buffer[offset+i+1];
				}
				code = CLELInstructionConvertor.ToCLELInstructions(byte_code,little_endian);
			}
			else if((type & MethodHeader.METHODTYPE_MASK) == MethodHeader.FAT_METHOD)
			{
				tiny = false;
				flags = Bits.ReadShort(buffer,offset,little_endian);
				size = type & 0xf;
				max_stack = Bits.ReadShort(buffer,offset+2,little_endian);
				code_size = Bits.ReadInt(buffer,offset+4,little_endian);
				local_var_sig_token = Bits.ReadInt(buffer,offset+8,little_endian);
				byte_code = new byte[code_size];
				for(i = 0;i < code_size;i++)
				{
					byte_code[i] = buffer[offset+i+12];
				}
				code = CLELInstructionConvertor.ToCLELInstructions(byte_code,little_endian);
				//read extra data section
				if((flags & MethodHeader.MORE_SECTS) == MethodHeader.MORE_SECTS)
				{
					MethodHeaderDataSectionRow row;
					MethodHeaderDataSection mhds;
					sects = new List<MethodHeaderDataSection>();

					bool another_section = true;
					byte sect_flags;
					int sect_off = Bits.AlignToFourByteBoundry(offset+12+code_size);
					while(another_section)
					{
						sect_flags = buffer[sect_off];
						if((sect_flags & MethodHeaderDataSection.SECT_EHTABLE) == MethodHeaderDataSection.SECT_EHTABLE)
						{
							bool sect_fat = (sect_flags & MethodHeaderDataSection.SECT_FATFORMAT) == MethodHeaderDataSection.SECT_FATFORMAT;
							int sect_size = 0;
							int rowflags = 0;
							int tryoffset = 0;
							int trylength = 0;
							int handleroffset = 0;
							int handlerlength = 0;
							int tokenoroffset = 0;
							int stop = 0;
							if(sect_fat)
							{
								//subtract 4 because of the 1 byte flags and 3 bytes for size
								sect_size = Bits.MakeInt(buffer[sect_off+1],buffer[sect_off+2],buffer[sect_off+3],0) - 4;
								mhds = new MethodHeaderDataSection(sect_flags,sect_size+4);
								sect_off += 4;
								stop = sect_off + sect_size;
								while(sect_off < stop)
								{
									rowflags = Bits.ReadInt(buffer,sect_off,little_endian);
									tryoffset = Bits.ReadInt(buffer,sect_off+4,little_endian);
									trylength = Bits.ReadInt(buffer,sect_off+8,little_endian);
									handleroffset = Bits.ReadInt(buffer,sect_off+12,little_endian);
									handlerlength = Bits.ReadInt(buffer,sect_off+16,little_endian);
									tokenoroffset = Bits.ReadInt(buffer,sect_off+20,little_endian);
									row = new SEHDataSectionRow(rowflags,tryoffset,trylength,handleroffset,handlerlength,tokenoroffset,true);
									mhds.AddRow(row);
									sect_off += MethodHeaderDataSection.FAT_DATA_ROW_SIZE;
								}
								sects.Add(mhds);
							}
							else
							{
								//subtract 2 for 1 byte flags and 1 byte length and 2 bytes of padding
								sect_size = buffer[sect_off+1] - 4;
								mhds = new MethodHeaderDataSection(sect_flags,sect_size+4);
								sect_off += 4;
								stop = sect_off + sect_size;
								while(sect_off < stop)
								{
									rowflags = (int)Bits.ReadShort(buffer,sect_off,little_endian);
									tryoffset = (int)Bits.ReadShort(buffer,sect_off+2,little_endian);
									trylength = (int)buffer[sect_off+4];
									handleroffset = (int)Bits.ReadShort(buffer,sect_off+5,little_endian);
									handlerlength = (int)buffer[sect_off+7];
									tokenoroffset = Bits.ReadInt(buffer,sect_off+8,little_endian);
									row = new SEHDataSectionRow(rowflags,tryoffset,trylength,handleroffset,handlerlength,tokenoroffset,false);
									mhds.AddRow(row);
									sect_off += MethodHeaderDataSection.TINY_DATA_ROW_SIZE;
								}
								sects.Add(mhds);
							}
						}
						else
						{
							throw new CLELParserException("Error method header data section unknown: "+ sect_flags + " offset: " + sect_off);
						}
						if((sect_flags & MethodHeaderDataSection.SECT_MORESECTS) != MethodHeaderDataSection.SECT_MORESECTS)	
							another_section = false;
					}
				}
			}
			else
			{
				throw new CLELParserException("Error method header type unknown: "+ type + " offset:" + offset);
			}

			return new MethodHeader(tiny,flags,size,max_stack,code_size,local_var_sig_token,code,sects);
		}

		public MethodHeaderDataSection GetSEHDataSection()
		{
			if(_data_sections == null)
				return null;

			MethodHeaderDataSection mhds_seh;
			int i,count = _data_sections.Count;
			byte flags;
			for(i = 0;i < count;i++)
			{
				mhds_seh = _data_sections[i];
				flags = mhds_seh.Flags;
				if((flags & MethodHeaderDataSection.SECT_EHTABLE) == MethodHeaderDataSection.SECT_EHTABLE)
				{
					return new MethodHeaderDataSection(mhds_seh.Flags,mhds_seh.SectionSize,mhds_seh.Rows);
				}
			}
			return null;
		}

		public void SetSEHDataSection(MethodHeaderDataSection seh)
		{
			if(seh == null)
				return;

			int i,count;
			MethodHeaderDataSection mhds_seh;
			count = _data_sections.Count;
			byte flags;
			for(i = 0;i < count;i++)
			{
				mhds_seh = _data_sections[i];
				flags = mhds_seh.Flags;
				if((flags & MethodHeaderDataSection.SECT_EHTABLE) == MethodHeaderDataSection.SECT_EHTABLE)
				{
					_data_sections[i] = new MethodHeaderDataSection(seh.Flags,seh.SectionSize,seh.Rows);
					return;
				}
			}
		}
	}
}
